// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.core {
	import actionml.Global;
	import actionml.core.ClassMethods;

	import flash.display.DisplayObject;
	import flash.geom.ColorTransform;	

	public class ObjectMethods {
		public static function initialize() {
			// Class methods must be initialize first so our
			// override of toString doesn't send a class into
			// an infinite recursion
			ClassMethods.initialize();			
			Global.initializeExtension(ObjectMethods, Object);
		}
		public static var instanceMethods:Object = {
			isA: function(s:String) {
				if (this["is" + s]) {
					return true; 
				} else {
					return false;
				}				
			},
			isAlready: function(s:String) {
				if (this["is" + s]) {
					return true; 
				} else {
					this["is" + s] = true;
					return false;
				}
			},
			toString: function(showProperties:Boolean=false) {
				if (this.constructor == Object || showProperties) {
					var properties:Array = [];
					for each (var p:String in this.getPropertyNames()) {
						properties.push(p + ": " + this[p]);
					}
					return "{\n\t" + properties.join(",\n\t") + "\n}";
				} else if (this.constructor == XML || this.constructor == XMLList) {
					return this.toXMLString();
				} else {
					return "[object " + this.constructor + "]";
				}
			},
			equals: function(o:*) {
				if (this.constructor == Object && o.constructor == Object) {
					for each (var p:String in o.getPropertyNames()) {
						if (!this.hasOwnProperty(p)) {
							return false;
						}
					}
					for each (var p2:String in this.getPropertyNames()) {
						if (!this[p2].equals(o[p2])) {
							return false;
						}
					}
					return true;
				} else {
					return this == o;
				}
			},
			clone: function(newValues:Object=null) {
				newValues ||= {};
				var c:Object = new (this.constructor)();
				c.setProperties(this.copyProperties());
				if (newValues) {
					c.setProperties(newValues);
				}
				return c;
			},
			
			getPropertyNames: function(includeReadOnly:Boolean=true) {
				var r:Array = this["constructor"].getPropertyNames(includeReadOnly);
				for (var s:String in this) {
					r.unshift(s);
				}
				return r;
			},
			getProperties: function() {
				var r:Object = {};
				for each (var p:String in this.getPropertyNames()) {
					r[p] = this[p];
				}
				return r;
			},
			copyProperties: function() {
				var o:* = {};
				for each (var p:String in this.getPropertyNames()) {
					o[p] = this[p];
				}
				return o;
			},
			setProperties: function(o:Object) {
				for each (var p:String in o.getPropertyNames()) {
					this[p] = o[p];
				}
				return this;
			},
			mergeProperties: function(o:Object) {
				var r:*;
				if (this.constructor == Array && o.constructor == Array) {
					r = [];	
				} else {
					r = {};
				}
				for each (var p:String in this.getPropertyNames()) {
					r[p] = this[p];
				}
				for each (p in o.getPropertyNames()) {
					r[p] = o[p];
				}
				return r;
			},
					
			setPropertyIfUndefined: function(p:String, value:*) {
				if (!(p in this)) this[p] = value;
				return this;
			},
			setPropertiesIfUndefined: function(o:Object) {
				for each (var p:String in o.getPropertyNames()) {
					this.setPropertyIfUndefined(p, o[p]);
				}
				return this;
			},
			
			tryMethod: function(method, ...args) {
				if (method in this && this[method] is Function) {
					try {
						this[method].apply(this, args);
					} catch (e:ArgumentError) { // built in methods won't take the args
						if (e.errorID == 1063) { // argument count mismatch
							this[method]();
						} else {
							throw e;
						}
					}
				}	
			},
			evaluateExpression: function(expression:Object) {
				if (expression is Array) {
					return expression.evaluateExpression(this);
				} else if (expression is Number) {
					return expression;
				} else if (expression is String && "get" + expression.toCapitalCase() in this && this["get" + expression.toCapitalCase()] is Function){ 
					return this["get" + expression.toCapitalCase()]();
				} else {
					return expression.compileExpression().evaluateExpression(this);
				}
			},
			getValueForKeypath: function(keypath:String) {
				if (keypath == "" || keypath == "this") return this;
				if (keypath in this) {
					return this[keypath];
				}
				var o:* = this;
				var i:int = 0;
				var keys:Array = keypath.split(".");
				if (keys.length > 0 && keys[0] == "this") {
					keys.shift();
					i++;
				}
				if (keys.length > 1 && keys[0] == "content") {
					if (this.root) {
						o = this.root["content"];
					} else {
						o = Global.document["content"];
					}
					keys.shift();
					i++;
				}
				if (keys.length > 1 && keys[0] == "parameters" && this["loaderInfo"]) {
					o = this.loaderInfo.parameters;
					keys.shift();
					i++;
				}	
				for each (var key:String in keys) {
					i++;
					if (o && key in o && o is XML) {
						if (o[key] is XMLList && o[key].length() == 1) {
							o = o[key][0];
						} else {
							o = o[key];
						}
					} else if ("get" + key["toCapitalCase"]() in o && o["get" + key["toCapitalCase"]()] is Function) {
						o = o["get" + key["toCapitalCase"]()]();
					} else if (o && key in o) {
						o = o[key];
					} else if (o && "getChildByName" in o && o["getChildByName"] is Function && o["getChildByName"](key)) {
						o = o["getChildByName"](key);
					} else if (this is DisplayObject && i == keys.length && key == "color") {
						var ct:ColorTransform = DisplayObject(o).transform.colorTransform;
						if (ct) {
							return (ct.redOffset << 16) + (ct.greenOffset << 8) + ct.blueOffset;
						} else {
							return null;
						}
					} else if (this is DisplayObject && i == keys.length - 1 && key.match(/^[A-Z]\w*Filter$/)) {
						var filterClass:Class = Class["forName"](key);
						if (filterClass) {
							var filters:Array = this.filters;
							var filter:Object;
							for each (var f:Object in filters) {
								if (f["constructor"] == filterClass) {
									filter = f;
								}
							}
							if (filter && keys[i] in filter) {
								return filter[keys[i]];
							}
						} 
						// TODO: trace error
						return null;
					} else if (i == 1 && this["parent"] && "getChildByName" in this["parent"] && this["parent"]["getChildByName"] is Function && this["parent"]["getChildByName"](key)) {
						return this.parent.getValueForKeypath(keypath);
					} else if (this is XML) {
						if (this.attribute(key) != undefined) {
							o = this.attribute(key);
						} else if (this.hasOwnProperty(key)) {
							if (o[key].length() == 1) {
								o = o[key][0];
							} else {
								o = o[key];
							}
						} else {
							return null;
						}
					} else {
						// TODO: trace error
						return null;
					}
				}
				return o;
			},
			setValueForKeypath: function(keypath:String, value:*) {
				if (keypath == "" || keypath == "this") throw new Error("Cannot set value for empty keypath or this.");
				var o:* = this;
				var keys:Array = keypath.split(".");
				var lastKey:String = keys.pop();
				if (this is DisplayObject && keys.length > 0 && keys[keys.length - 1].match(/^[A-Z]\w*Filter$/)) {
					var filterClass:Class = Class["forName"](keys.pop());
					o = this.getValueForKeypath(keys.join("."));
					if (o == null || !filterClass) {
						// TODO trace error
						return this;
					}
					var filters:Array = o.filters;
					var filter:Object;
					for each (var f:Object in filters) {
						if (f["constructor"] == filterClass) {
							filter = f;
						}
					}
					if (!filter) {
						filter = new filterClass();
						filters = this.filters.append(filter);
					} 
					filter[lastKey] = value;
					o.filters = filters;				
				} else {
					o = this.getValueForKeypath(keys.join("."));
					if (o == null) {
						// TODO trace error
						return this;
					}
					if ("set" + lastKey["toCapitalCase"]() in o && o["set" + lastKey["toCapitalCase"]()] is Function) {
						o["set" + lastKey["toCapitalCase"]()](value);
					} else if (this is DisplayObject && lastKey == "color") {
						var ct:ColorTransform = DisplayObject(o).transform.colorTransform || new ColorTransform();
						ct.redMultiplier = 0;
						ct.greenMultiplier = 0;
						ct.blueMultiplier = 0;
						ct.redOffset = value >> 16;
						ct.greenOffset = (value >> 8) & 0xff;
						ct.blueOffset = value & 0xff;
						DisplayObject(o).transform.colorTransform = ct;
					} else {
						o[lastKey] = value;
					}
				}
				return this;
			}
		};
	}
}